﻿using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;

namespace AMEE1
{
    /// <summary>
    /// RL-Zustand = Karte mit Unebenheiten je Abschnitt plus Zielrichtung (Sektornummer) plus minimale Schrittzahl zum Ziel.
    /// </summary>
    class State : IState, ICloneable/*, IEnumerable<double>*/
    {
        int[] _merkmale;

        #region Properties

        /// <summary>
        /// Gets or sets the zielrichtung.<para>
        /// Nummer des Sektors, der in Richtung Ziel zeigt</para>
        /// </summary>
        /// <value>
        /// The zielrichtung.
        /// </value>
        public int Zielrichtung { get; internal protected set; }

        /// <summary>
        /// Gets or sets minimale Anzahl von Schritten bis zum Ziel.
        /// </summary>
        /// <value>
        /// The minimum schritte.
        /// </value>
        public int MinSchritte { get; set; }

        /// <summary>
        /// The unebenheiten.<para>
        /// Unebenheitsmaß je Abschnitt. Array-Index = Abschnittsnummer</para>
        /// </summary>
        public double[] Unebenheiten { get; internal protected set; }

        #endregion

        #region Konstruktor(en)

        State() {
            // TODO State-Konstruktor - irgendwelche initialisierungen?
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        /// <param name="ziel">Vector Agent->Ziel</param>
        /// <param name="unebenheiten">The unebenheiten.</param>
        /// <param name="featureCombi">The feature combination.</param>
        public State(Vector ziel, double[] unebenheiten, DimensionCombination featureCombi) {
            Zielrichtung = featureCombi.DirectionFeatureFunction(ziel);
            Unebenheiten = unebenheiten;
            MinSchritte = featureCombi.DistanceFeatureFunction(ziel);
            _merkmale = featureCombi.GetFeatureSet(this);
            FeaturedID = this.GetHashCode();
        }

        #endregion

        #region IClonable

        object ICloneable.Clone() {
            State clone = this.Clone();
            return clone;
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public State Clone() {
            State clone = new State { Zielrichtung = this.Zielrichtung, MinSchritte = this.MinSchritte, FeaturedID = this.FeaturedID };
            clone.Unebenheiten = new double[this.Unebenheiten.Length];
            this.Unebenheiten.CopyTo(clone.Unebenheiten, 0);
            return clone;
        }

        #endregion

        /// <summary>
        /// Gibt die merkmalsbasierte ID dieses Zustands.
        /// Zwei Zustände haben dieselbe ID, wenn sie dieselben Merkmale besitzen.
        /// Die ID ist 0, wenn noch keine Merkmale gesetzt wurden.
        /// </summary>
        /// <value>
        /// The featured identifier.
        /// </value>
        internal int FeaturedID {
            get;
            private set;
        }

        /// <summary>
        /// Gets the features.
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<int> GetFeatures() {
            if (_merkmale == null) {
                yield break;
            }
            for (int i = 0; i < _merkmale.Length; i++) {
                yield return _merkmale[i];
            }
        }

        internal int FeatureCount {
            get {
                if (_merkmale == null)
                    return 0;
                return _merkmale.Length;
            }
        }


        /// <summary>
        /// Setzt (einmal!) die Merkmale des angegebenen Zustands.
        /// </summary>
        /// <param name="s">The state.</param>
        /// <param name="config">The configuration.</param>
        /// <returns>true, wenn die Merkmale nicht null sind und zuvor nocht nicht gesetzt wurden.</returns>
        /// <remarks>
        /// state.FeaturedID ist != 0, wenn Merkmale gesetzt sind.
        /// </remarks>
        private static bool SetFeatures(State s, MapConfiguration config) {
            if (!(s._merkmale == null && s.FeaturedID == 0)) {
                return false;
            }
            // TODO: Zielentfernung als Feature hinzufügen!!!
            s._merkmale = GibMerkmalsVektor(s, config);
            s.FeaturedID = s.GetHashCode();
            return true;
        }

        /// <summary>
        /// Dickes TODO. Die Berechnung nochmal prüfen (vgl. DampfrossKontinuum.UmgebungC)
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="config">The configuration.</param>
        /// <returns></returns>
        private static int[] GibMerkmalsVektor(State s, MapConfiguration config) {
            int anzAbschnitte = config.Abschnitte;
            int anzZielrichtungen = config.Zielrichtungen;
            double[] unebenheiten = s.Unebenheiten;
            MultiTiling uTiling = config.BumpinessTiling;
            // Berechnung des theta-Index - TODO: mehrere Tilings = mehrere indizes!
            //int theta_index = aktion * weltConfig.Dimension + state.GibZustandsID(); // gibZustandIndex(state);
            //return new int[] { theta_index };

            // zu jedem Abschnitt die Features von dessen u-Wert abrufen und
            // gelieferte Indizes umrechnen.
            // Bsp. 6 Tiles je MultiTiling, dann gilt für jeden Index i in GetFeatureSet(Ab2.uWert)
            // Theta-Index = AbNr. * 6 + i = 2*6 + i = 12i.


            // Für JEDE Aktion gibt es ein FeatureSet des Zustands s =>
            // Für jeden bisherigen ThetaIndex gilt: theta_idx = AktionsNr * GesamtzahlAktionen + theta_idx
            // ODER
            // je Aktion ein Theta-Vektor / FeatureSet
            //
            // Also insgesamt: 
            // für alle Abschnitte in state
            //   für alle TilingIndizes zu U(Abschnitt_i)
            //     index = (AktionsNr * GesamtzahlAktionen) + (AbNr. * TilesJeTiling + TileIndex)
            //     füge index zu FeatureSet hinzu
            List<int> result = new List<int>();
            int indexOffset = 0;
            // zu jedem Abschnitt Unebenheits-Merkmale ermitteln
            for (int abschnittsNr = 0; abschnittsNr < anzAbschnitte; abschnittsNr++) {
                // Merkmale gemäß Tilings-Organisation:
                double u_ab = unebenheiten[abschnittsNr];
                int[] merkmale_u_ab = uTiling.GetFeatureNumbers(u_ab);
                // MerkmalIndizes anpassen gemäß Merkmale je Abschnitt:
                indexOffset = abschnittsNr * anzAbschnitte;
                for (int i = 0; i < merkmale_u_ab.Length; i++) {
                    merkmale_u_ab[i] += indexOffset;
                }
                result.AddRange(merkmale_u_ab);
                
            }

            AddRichtungsmerkmal(result, config, s);
            AddEntfernungsmerkmal(result, config, s);
            
            return result.ToArray();
        }

        private static void AddRichtungsmerkmal(List<int> merkmale, MapConfiguration config, State s) {
            int indexOffset = config.Abschnitte * config.Abschnitte;
            int zielMerkmal = indexOffset + s.Zielrichtung;
            merkmale.Add(zielMerkmal);
        }

        private static void AddEntfernungsmerkmal(List<int> merkmale, MapConfiguration config, State s) {
            // plus das eine Entfernungsmerkmal:
            int indexOffset = config.Abschnitte * config.Abschnitte + config.Zielrichtungen;
            int f_i = config.EntfernungsTiling.GetPartitionNumber(s.MinSchritte);
            merkmale.Add(indexOffset + f_i);
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString() {
            StringBuilder sb = new StringBuilder("State[");
            if (this.Unebenheiten == null) {
                sb.Append("null");
            }
            else if (Unebenheiten.Length == 0) {
                sb.Append("-");
            }
            else {
                sb.Append(Unebenheiten[0].ToString("F3"));
                for (int i = 1; i < Unebenheiten.Length; i++) {
                    sb.Append(", ");
                    sb.Append(Unebenheiten[0].ToString("F3"));
                }
            }
            sb.Append("; ZR").Append(Zielrichtung);
            sb.Append("]");
            return sb.ToString();
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode() {
            if (_merkmale == null)
                return Int32.MinValue;
            if (_merkmale.Length == 0)
                return -1;
            int hc = 11;
            for (int i = 0; i < _merkmale.Length; i++) {
                hc = 31 * hc + _merkmale[i];
            }
            return hc;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
        /// Note: zwei Unebenheiten sind gleich, wenn die Differenz kleiner als 7*Epsilon ist.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj) {
            // !!! Gegebenenfalls lieber auf Gleichheit der Merkmale prüfen !!!!
            // Aber Achtung. Merkmale müssen extra mit SetFeatures von außen gesetzt werden.
            if (obj == null || ! (obj is State)) {
                return false;
            }
            State other = (State)obj;
            if (other.Zielrichtung != this.Zielrichtung) {
                return false;
            }
            if (other.MinSchritte != this.MinSchritte)
                return false;
            if (other.Unebenheiten == null ^ this.Unebenheiten == null) {
                return false;
            }
            else if (other.Unebenheiten != null) {
                if (other.Unebenheiten.Length != this.Unebenheiten.Length) {
                    return false;
                }
                else {
                    for (int i = 0; i < Unebenheiten.Length; i++) {
                        if (Math.Abs(other.Unebenheiten[i] - this.Unebenheiten[i]) > _equalityPrecision) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }
        const double _equalityPrecision = 7 * Double.Epsilon;

    }
}
